/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#ifndef __MPQ_ARCHIVE_H__
#define __MPQ_ARCHIVE_H__

#include <string>
#include <map>
#include <vector>
#include "StormLib.h"
#include "OgreString.h"
#include "OgreStringVector.h"

#include "FileBase.h"

namespace PQGameCore
{
	class MPQArchive;
	class MPQListFile;

	/*Represent a file(dir) node in MPQ file system
	*/
	class MPQTreeNode
	{
	public:
		typedef std::vector<MPQTreeNode*> NodeList;
		friend class MPQListFile;
		inline bool isLeaf()
		{
			return _children.size()==0;
		}

		inline static bool isLeaf(MPQTreeNode* node)
		{
			if(node){
				return node->isLeaf();
			}
			return false;
		}

		inline bool isChildExsit(std::string name)
		{
			int size=_children.size();
			for(int i=0;i<size;i++){
				if(_children[i]!=0){
					if(_children[i]->getName()==name){
						return true;
					}
				}
			}
			return false;
		}

		inline MPQTreeNode* addChild(std::string name)
		{
			MPQTreeNode* child=getChild(name);
			if(child){
				return child;
			}
			child=new MPQTreeNode(name);

			_children.push_back(child);
			child->setParent(this);
			return child;
		}

		inline MPQTreeNode* addChild(MPQTreeNode* child)
		{
			if(!child){
				return 0;
			}
			_children.push_back(child);
			child->setParent(this);
			return child;
		}


		inline MPQTreeNode* getChild(std::string name)
		{
			int size=_children.size();
			for(int i=0;i<size;i++){
				if(_children[i]!=0){
					if(_children[i]->getName()==name){
						return _children[i];
					}
				}
			}
			return 0;
		}

		inline int getChildrenCount()
		{
			return _children.size();
		}

		inline std::string getChildName(int index)
		{
			if(index<0||index>=_children.size()){
				return "";
			}
			return _children[index]->getName();
		}

		inline MPQTreeNode* getChild(int index)
		{
			if(index<0||index>=_children.size()){
				return 0;
			}
			return _children[index];
		}

		inline bool isRoot()
		{
			return _parent==0;
		}

		inline std::string getName()
		{
			return _name;
		}
	protected:
		MPQTreeNode():_name("/"),_parent(0){}
		MPQTreeNode(std::string name):_name(name),_parent(0){}

		inline void setParent(MPQTreeNode* parent)
		{
			_parent=parent;
		}
		
	private:
		std::string _name;//
		NodeList _children;
		MPQTreeNode* _parent;

	};

	
	/*Represent a file in MPQ archive
	*/
	class MPQFile
		: public PQEngine::FileBase
	{
	public:
		friend class MPQArchive;
	protected:
		// filenames are not case sensitive
		MPQFile(const char* filename,void *buffer,int size);
	public:
		~MPQFile();
		inline std::string getFileName()
		{
			return _filename;
		}

		inline bool eof()
		{
			return _eof;
		}


		size_t read(void* dest, size_t bytes);
		int getSize();
		int getPos();
		void* getBuffer();
		
		void seek(int offset);
		void seekRelative(int offset);

		int getPosition()
		{
			return _pointer;
		}

		void* getData()
		{
			return _buffer;
		}


		virtual void open()
		{
			_pointer=0;
			_opened=true;
		}
		

		virtual void close()
		{
			_pointer=0;
			_opened=false;
		}

		virtual void advance(int offset)
		{
			_pointer+=offset;
		}

	private:
		char* _filename;
		bool _eof;
		void *_buffer;
		int _pointer;
		int _size;
		bool _open;
	};

	/*List files in this mpq archive in tree structure.
	@remark
		In general, MPQ archives don't contain names of archived files.
		The files are stored by their name's hash value.
		The algorithm of calculating hash value is one-way only, 
		so it's impossible to get back the original archived file name from the hash value.
	*/
	class MPQListFile
	{
	public:
		typedef std::map<std::string,MPQTreeNode*> NodeList;

		MPQListFile(MPQFile* file);
		void addLine(std::string line);

		MPQTreeNode* getTreeNode(std::string path);
		MPQTreeNode* getRoot()
		{
			return _root;
		}
		/*Add path to tree structure
		*/
		MPQTreeNode* addNode(std::string line);

		inline static std::string getPathLastNode(std::string path)
		{
			Ogre::StringVector strs=Ogre::StringUtil::split(path,"\\");
			if(strs.size()<1){
				return "";
			}

			return strs[strs.size()-1];
		}

		inline static bool isLeafPath(std::string path)
		{
			Ogre::StringVector strs=Ogre::StringUtil::split(path,"\\");	
			int size=strs.size();
			if(size<=1){
				return true;
			}else{
				return false;
			}
		}

		inline static std::string getParentPath(std::string path)
		{
			Ogre::StringVector strs=Ogre::StringUtil::split(path,"\\");
			int size=strs.size();
			if(size<=1){
				return "";
			}

			std::string str=strs[0];
			for(int i=1;i<size-1;i++){
				str+="\\";
				str+=strs[i];
			}
			return str;
		}

	private:
		NodeList _nodeList;
		MPQFile* _file;
		MPQTreeNode* _root;//root node;

	};

	/*class that manage mpq archive
	*/
	class MPQArchive
	{
	public:
		typedef std::map<std::string,MPQArchive*> ArchiveList;
		typedef std::map<int,std::string> IDList;

	protected:
		MPQArchive(std::string filename,HANDLE handel);
	public:
		static MPQArchive* openArchive(std::string filename);
		static MPQArchive* getArchive(std::string filename);

		MPQTreeNode* getTreeNode(std::string path);
		
		inline int getId()
		{
			return _id;
		}

		inline void getDir()
		{
		}

		~MPQArchive();

		inline std::string getArchivePath()
		{
			return _filename;
		}

		static int extractFile(char* archiveName, char* archivedFile, char* fileName);
		int extractFile(char* archivedFile, char* fileName);

		//open mpq file in current loaded mpq archive
		MPQFile* openFile(const char* filename);

		//open mpq file in specific mpq archive
		static MPQFile* openFile(MPQArchive* archive, const char* filename);

		bool fileExist(const char* filename);
		int getFileSize(const char* filename);// Used to do a quick check to see if a file is corrupted

		/*Get MPQ archive handle
		*/
		HANDLE getHandle()
		{
			return _handle;
		}
		

		inline MPQArchive* get(int id)
		{
			IDList::iterator it=_idList.find(id);
			if(it!=_idList.end()){
				return _list[it->second];
			}
			return 0;
			
		}

		void close();
	private:
		void initListFile();//for search file
	private:
		static ArchiveList _list;
		static IDList _idList;
		MPQListFile* _listFile;//for search files in mpq
		std::string _filename;//archive path
		int _id;

		HANDLE _handle;//file handle
	};
}

#endif

